package cgi

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

type Handler struct {
	Path 	string 		// 程序的路径,可以是绝对路径也可以是相对路径
	Dir 	string		// getwd 工作目录
	Args 	[]string 	// 参数
	Env 	[]string	// 环境变量
	Stderr  io.Writer	// 错误日志记录
	Logger  *log.Logger // 日志句柄

}

// 子进程的错误输出
func (h *Handler) stderr() io.Writer {
	if h.Stderr != nil {
		return h.Stderr
	}
	return os.Stderr
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 1. 初始化需要传递给cgi程序的标准输入、环境变量
	// 1.1 得到程序的实际路径
	var cwd, path string
	if h.Dir != "" {
		path = h.Path
		cwd = h.Dir
	}else {
		cwd, path = filepath.Split(h.Path)
	}
	// 1.2 环境变量设置
	port := "80"
	pattern := regexp.MustCompile(`:(\d+)$`)
	if matches := pattern.FindStringSubmatch(r.Host); len(matches) > 0 {
		port = matches[1];
	}

	execenv := []string{
		"SERVER_PROTOCOL=HTTP/1.1",
		"SERVER_NAME=" + r.Host,
		"SERVER_PORT=" + port,
		"SERVER_SOFTWARE=go",
		"REQUEST_METHOD=" + r.Method,
		"QUERY_STRING=" + r.URL.RawQuery,
		"REQUEST_URI=" + r.URL.RequestURI(),
		"PATH_INFO=" + r.URL.Path,
		"SCRIPT_NAME=" + r.URL.Path,
		"SCRIPT_FILENAME=" + r.URL.Path,
		"GATEWAY_INTERFACE=1.1",
	}

	if remoteIp, remotePort, err := net.SplitHostPort(r.RemoteAddr); err != nil {
		execenv = append(execenv,"REMOTE_ADDR="+remoteIp,"REMOTE_HOST="+remoteIp, "REMOTE_PORT="+remotePort)
	} else {
		execenv = append(execenv,"REMOTE_ADDR="+r.RemoteAddr,"REMOTE_HOST="+r.RemoteAddr)
	}

	if r.TLS != nil {
		execenv = append(execenv, "HTTPS=on")
	}

	// 1.3 把header中的信息添加到环境变量中
	for k,v := range r.Header {
		k := strings.Map(upperCaseAndUnderscore, k)
		joinStr := ", "
		if k == "COOKIE" {
			joinStr = "; "
		}
		execenv = append(execenv, "HTTP_"+k+"="+strings.Join(v,joinStr))
	}

	if r.ContentLength > 0 {
		execenv = append(execenv, fmt.Sprintf("CONTENT_LENGTH=%d", r.ContentLength))
	}

	if ctype := r.Header.Get("Content-Type"); ctype != "" {
		execenv = append(execenv, "CONTENT_TYPE=" + ctype)
	}

	envPath := os.Getenv("PATH");
	execenv = append(execenv,"PATH="+envPath)
	if h.Env!=nil && len(h.Env) > 0 {
		execenv = append(execenv, h.Env...)
	}

	// 定义一个函数类型的变量
	innerError := func(e error){
		w.WriteHeader(http.StatusInternalServerError)
		h.printf("CGI error: %v", e)
	}
	// 2. 创建子进程，执行子程序
	cmd := &exec.Cmd{
		Path: path,
		Args: append([]string{h.Path},h.Args...),
		Dir:  cwd,
		Env:  execenv,
		Stderr: h.stderr(),  // 设置错误输出句柄
	}
	stdOut, err:= cmd.StdoutPipe()
	if err != nil {
		innerError(err)
		return
	}

	cmd.Stdin = r.Body
	if e := cmd.Start(); e != nil {
		innerError(e)
		return
	}
	defer cmd.Wait()
	defer stdOut.Close()
	// 3. 对返回进行解析
	reader := bufio.NewReader(stdOut)
	headers := make(http.Header)
	statusCode := 0
	headerLines := 0
	sawBlankLine := false

	for {
		// 循环读取数据
		line, isPrefix, err := reader.ReadLine()
		if isPrefix {
			innerError(errors.New("cgi: long header line from subprocess."))
			return
		}
		if err == io.EOF {
			break;
		}
		if err != nil {
			innerError(err)
			return
		}
		// 如果遇到空行，则表示header结束
		if len(line) == 0 {
			sawBlankLine = true
			break
		}
		// header行数递增
		headerLines++
		// 将header行进行拆分
		parts := strings.SplitN(string(line), ":", 2)
		if len(parts) < 2 {
			// 忽略不正确的header
			h.printf("cgi: bad header line: %s", string(line))
			continue
		}

		header, value := parts[0], parts[1]
		header = strings.TrimSpace(header)
		value = strings.TrimSpace(value)
		switch {
		case header == "Status":
			if len(value) < 3 {
				innerError(errors.New("cgi: bad status code"))
				return
			}
			code, err := strconv.Atoi(value[0:3])
			if err != nil {
				innerError(errors.New("cgi: code convert error"))
				return
			}
			statusCode = code
		default:
			headers.Add(header, value)
		}
	}
	if headerLines == 0 || !sawBlankLine {
		innerError(errors.New("cgi: no headers"))
		return
	}

	// 判断是否为location
	if location := headers.Get("Location"); location != "" {
		statusCode = http.StatusFound
	}

	// 设置默认的content-type
	if headers.Get("Content-Type") == "" {
		headers.Set("Content-Type", "text/html; charset=utf8")
	}

	if statusCode == 0 {
		statusCode = http.StatusOK
	}

	for k, vv := range headers {
		for _, v := range vv {
			w.Header().Add(k,v)
		}
	}

	w.WriteHeader(statusCode)

	_, err = io.Copy(w, reader)
	if err != nil {
		h.printf("cgi: copy error: %v", err)
		cmd.Process.Kill()
	}
}


func (h *Handler) printf(format string, v ...interface{}) {
	if h.Logger != nil {
		h.Logger.Printf(format, v...)
	} else {
		log.Printf(format, v...)
	}
}

func upperCaseAndUnderscore(r rune) rune {
	switch {
	case r > 'a' && r < 'z' :
		return r - ('a' - 'A')
	case r == '-':
		return '_'
	case r == '=':
		return '_'
	}
	return r
}


